﻿using PluginNET.error;
using PluginNET.events;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace PluginNET
{
    /// <summary>
    /// 插件管理器
    /// </summary>
    /// <typeparam name="InterfaceClass">插件方法的接口定义类，必须使用 abstract 修饰</typeparam>
    public sealed class PluginManager<InterfaceClass> : IDisposable where InterfaceClass : class
    {
        #region 字段
        /// <summary>
        /// 存储插件的目录
        /// </summary>
        private string pluginFolder;

        /// <summary>
        /// 文件系统监视工具
        /// </summary>
        private FileSystemWatcher watcher;

        /// <summary>
        /// 接口类的类型
        /// </summary>
        private Type interfaceType;

        /// <summary>
        /// 执行插件的应用程序域
        /// </summary>
        private AppDomain domain;


        #endregion

        #region 事件

        /// <summary>
        /// 程序集加载前的事件
        /// </summary>
        public event PluginEvent<PluginAssemblyLoadingArgs> OnAssemblyLoading;

        /// <summary>
        /// 程序集加载后的事件
        /// </summary>
        public event PluginEvent<PluginAssemblyLoadedArgs> OnAssemblyLoaded;

        /// <summary>
        /// 插件实例创建前的事件
        /// </summary>
        public event PluginEvent<PluginInstanceCreatingArgs> OnInstanceCreating;

        /// <summary>
        /// 插件实例创建后的事件
        /// </summary>
        public event PluginEvent<PluginInstanceCreatedArgs<InterfaceClass>> OnInstanceCreated;

        /// <summary>
        /// 加载插件失败的事件，具体的错误请通过参数<paramref name="e"/>的属性 ErrorType
        /// 错误类型参见 <see cref="PluginErrorTypes"/>
        /// </summary>
        public event PluginErrorEvent<PluginErrorEventArgs> OnError;

        #endregion

        /// <summary>
        /// 初始化插件管理器
        /// </summary>
        /// <param name="domain">使用插件的应用程序域</param>
        /// <param name="pluginFolder">插件所在目录，绝对路径或相对路径</param>
        public PluginManager(AppDomain domain, string pluginFolder = "plugin")
        {
            // 检查泛型参数类型
            interfaceType = typeof(InterfaceClass);
            if (!interfaceType.IsClass || !interfaceType.IsAbstract)
            {
                throw new PluginInvalidGeneralTypeException();
            }

            this.domain = domain;

            this.pluginFolder = Path.IsPathRooted(pluginFolder)
                ? pluginFolder : Path.Combine(domain.BaseDirectory, pluginFolder);

            // 如果插件目录不存在，就创建
            if (!Directory.Exists(this.pluginFolder))
            {
                Directory.CreateDirectory(this.pluginFolder);
            }
        }

        /// <summary>
        /// 加载插件目录下的所有插件
        /// </summary>
        /// <param name="filter">插件过滤器，返回false表示不加载这个插件</param>
        public void Load(Func<string, bool> filter = null)
        {
            // 遍历插件目录下的dll
            foreach (var filename in Directory.GetFiles(pluginFolder, "*.dll") // 筛选dll文件
                .Where(filename => filter == null || filter(filename)) // 如果调用Load时有过滤器，则调用过滤器                
                )
            {
                LoadPlugin(filename);
            }
        }

        /// <summary>
        /// 加载插件
        /// </summary>
        /// <param name="filename"></param>
        private void LoadPlugin(string filename)
        {
            var assembly = LoadAssembly(filename);
            if (assembly == null)
            {
                return;
            }
            LoadPluginType(filename, assembly);
        }

        /// <summary>
        /// 加载程序集
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        private Assembly LoadAssembly(string filename)
        {
            // 加载程序集
            try
            {
                // 加载程序集前发出一个事件，如果程序不想用这个文件呢？对吧
                if (EmitPluginEvent(OnAssemblyLoading, new PluginAssemblyLoadingArgs()
                {
                    FileName = filename
                }))
                {
                    return null;
                }

                // 加载
                var assembly = Assembly.LoadFrom(filename);

                // 加载后再发出一个事件，如果程序现在才知道不想用这个文件呢？对吧
                if (EmitPluginEvent(OnAssemblyLoaded, new PluginAssemblyLoadedArgs()
                {
                    FileName = filename,
                    Assembly = assembly
                }))
                {
                    return null;
                }

                return assembly;
            }
            catch (Exception ex)
            {
                // 啊哦  程序集加载失败了
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Exception = ex,
                    ErrorType = PluginErrorTypes.InvalidManagedDllFile
                });
                return null;
            }
        }

        /// <summary>
        /// 从程序集加载插件的实现类（仅加载第一个）
        /// </summary>
        /// <param name="filename">这里搞个文件名，只为了在发出事件时可以加上文件名</param>
        /// <param name="assembly">程序集对象</param>
        private void LoadPluginType(string filename, Assembly assembly)
        {
            Type[] types;
            try
            {
                // 加载程序集内的所有类型
                types = assembly.GetTypes();
            }
            catch (Exception ex)
            {
                // 啊哦  类型加载失败了  这里搞个事件，为了让程序知道有个插件加载失败了
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly,
                    Exception = ex,
                    ErrorType = PluginErrorTypes.CannotLoadClassTypes
                });
                return;
            }

            // 查找继承自指定接口的第一个类
            var clazz = types.FirstOrDefault(t => t.IsSubclassOf(interfaceType));

            // 插件内没有找到继承接口的类  可能不是插件
            if (clazz == null)
            {
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly,
                    ErrorType = PluginErrorTypes.ImplementionClassNotFound
                });
                return;
            }

            // 检查类型修饰符
            // 这个类型不是类class或是abstract 或不是 public
            if (!clazz.IsClass || clazz.IsAbstract || !clazz.IsPublic)
            {
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly,
                    ClassType = clazz,
                    ErrorType = PluginErrorTypes.IllegalClassDefinition
                });
                return;
            }

            // 检查是否存在无参的构造
            if (clazz.GetConstructors().All(con => con.GetParameters().Length > 0))
            {
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly,
                    ClassType = clazz,
                    ErrorType = PluginErrorTypes.ZeroParameterConstructorNotFound
                });
                return;
            }

            try
            {
                // 创建实例前，发出事件，可以阻止一下
                EmitPluginEvent(OnInstanceCreating, new PluginInstanceCreatingArgs()
                {
                    Assembly = assembly,
                    ClassType = clazz
                });
                // 创建实例
                var instance = assembly.CreateInstance(clazz.FullName);
                if (instance == null)
                {
                    // 创建实例没有报错，但实例为null，发出事件
                    EmitPluginErrorEvent(new PluginErrorEventArgs()
                    {
                        FileName = filename,
                        Assembly = assembly,
                        ClassType = clazz,
                        ErrorType = PluginErrorTypes.ImplementionClassNotFound
                    });
                    return;
                }

                // 创建实例成功后，发出事件
                EmitPluginEvent(OnInstanceCreated, new PluginInstanceCreatedArgs<InterfaceClass>()
                {
                    Assembly = assembly,
                    ClassType = clazz,
                    Instance = instance as InterfaceClass
                });
            }
            catch (Exception ex)
            {
                // 创建实例时发生了异常，发出事件
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly,
                    ClassType = clazz,
                    ErrorType = PluginErrorTypes.InstanceCreateFailed,
                    Exception = ex
                });
            }
        }

        /// <summary>
        /// 开始监视插件目录，当目录下有新的dll时，会自动加载程序集并通知应用程序
        /// </summary>
        public void Watch()
        {
            // 创建watcher实例
            if (watcher == null)
            {
                watcher = new FileSystemWatcher(pluginFolder, "*.dll");
                watcher.Created += watcherEventHandler;
                watcher.Renamed += watcherEventHandler;
            }
            // 已经在监听
            if (watcher.EnableRaisingEvents)
            {
                return;
            }

            // 开始监听
            watcher.EnableRaisingEvents = true;
        }

        /// <summary>
        /// 停止监视插件目录
        /// </summary>
        public void StopWatch()
        {
            // 在监听才停止
            if (watcher.EnableRaisingEvents)
            {
                watcher.EnableRaisingEvents = false;
            }
        }

        /// <summary>
        /// 当插件目录有新的dll时或有文件改名为.dll时的处理函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void watcherEventHandler(object sender, FileSystemEventArgs e)
        {
            var fi = new FileInfo(e.FullPath);
            // 检查文件是否存在
            if (!fi.Exists)
            {
                return;
            }

            // 检查文件是否可用
            while (true)
            {
                try
                {
                    fi.OpenRead().Close();
                    // 没有抛出异常，表示文件可以加载了
                    break;
                }
                catch (Exception)
                {
                    // 文件还没有准备好
                    Thread.Sleep(1000);
                }
            }

            LoadPlugin(e.FullPath);
        }

        /// <summary>
        /// 发出插件事件的公共函数
        /// </summary>
        /// <returns>返回true阻止插件继续加载</returns>
        private bool EmitPluginEvent<ArgType>(PluginEvent<ArgType> eventName, ArgType arg)
            where ArgType : PluginEventArgs
        {
            if (eventName == null)
            {
                return false;
            }

            eventName.Invoke(this, arg);

            return arg.Cancel;
        }

        /// <summary>
        /// 发出插件错误事件的公共函数
        /// </summary>
        private void EmitPluginErrorEvent(PluginErrorEventArgs arg)
        {
            if (OnError == null)
            {
                return;
            }

            OnError.Invoke(this, arg);
        }

        public void Dispose()
        {

            StopWatch();
            watcher.Dispose();

            pluginFolder = null;

            OnAssemblyLoading = null;

            interfaceType = null;
        }
    }
}
